/*
 ============================================================================
 Name        : SecondPass.c
 Author      : Sherry Krikszer
 Version     : 1.0
 Copyright   :
 Description : in C, Ansi-style
 ============================================================================
 */

#include <stdlib.h>
#include <string.h>

#include "SecondPass.h"
#include "CommandParser.h"
#include "AssemblerCommon.h"

/* ----------------------------------------------------------------------------
 *	Constants Definition
 * ----------------------------------------------------------------------------*/

#define EXTERN_ADDRESS 	0

#define DEST_OPERAND 	0
#define SOURCE_OPERAND 	1

/* ----------------------------------------------------------------------------
 *	Constants Definition End
 * ----------------------------------------------------------------------------*/

/* ----------------------------------------------------------------------------
 *	Function Definition
 * ----------------------------------------------------------------------------*/

Symbol* GetSymbolByName(char* symbolName, SymbolTable* currTable)
{
	Symbol* matchingSymbolName = NULL;

	int index;

	for (index = 0; index < currTable->SC; index++)
	{
		if (strcmp(symbolName, currTable->symbols[index].symbolName) == EQUAL_STRINGS)
		{
			matchingSymbolName = &(currTable->symbols[index]);
			break;
		}
	}

	return matchingSymbolName;
}

int calculateOperandAffectiveAddress(Symbol* symbol, SymbolTable* symbolTable)
{
	int affectiveAddress;

	switch (symbol->symbolType)
	{
		case CODE:
			affectiveAddress = symbol->offset + STARTING_MEMORY_ADDRESS;
			break;
		case DATA:
			affectiveAddress = symbol->offset + symbolTable->symbolBaseAdress;
			break;
		case ENTRY:
			/* No Handling */
			break;
		case EXTERN:
			affectiveAddress = EXTERN_ADDRESS;
			break;
		default:

			break;
	}
	
	return affectiveAddress;
}

Boolean ParseSecondPassOperand(int memoryBaseAddress, InstructionMemoryWord* memoryWord, int instructionOffset, Operand* operand,
		SymbolTable* symbolTable, SymbolTable* externSymbolTable, char* errorMessage)
{
	Boolean parseSucceeded = FALSE;

	switch (operand->operandAdressingMethod)
	{
		case IMMEDIATE_ADDRESSING:
		case REGISTER_ADDRESSING:
			/* No Additional handling required */
			parseSucceeded = TRUE;
			break;
		case DIRECT_ADDRESSING:
			parseSucceeded = ParseOperandBaseWord(operand, symbolTable, externSymbolTable, errorMessage);
			break;
		case DOUBLE_INDEX_ADDRESSING:
			parseSucceeded = ParseOperandBaseWord(operand, symbolTable, externSymbolTable, errorMessage);

			if (parseSucceeded == FALSE)
			{
				break;
			}

			parseSucceeded = ParseOperandExtendWord(operand, symbolTable, externSymbolTable, errorMessage);
			break;
		case INDEX_ADDRESSING:
		{
			Symbol* matchingSymbol = NULL;
			/* Search for the given symbol in the local symbol table */
			parseSucceeded = ParseOperandBaseWord(operand, symbolTable, externSymbolTable, errorMessage);

			if (parseSucceeded == FALSE)
			{
				break;
			}

			/*check for second label*/
			/* Search for the given symbol in the local symbol table cannot be extern */
			matchingSymbol = GetSymbolByName(operand->operandExtendSymbol, symbolTable);

			if (matchingSymbol != NULL)
			{
				operand->operandExtendWord = calculateOperandAffectiveAddress(matchingSymbol, symbolTable) - (instructionOffset + memoryBaseAddress);
				operand->linkingOptionExtendWord = ABSOLUTE;
				parseSucceeded = TRUE;
			}
			else
			{
				sprintf(errorMessage, "undefined symbol");
				parseSucceeded = FALSE;
			}
		}
		default:
			break;
	}

	return parseSucceeded;
}

Boolean ParseOperandBaseWord(Operand* operand, SymbolTable* symbolTable, SymbolTable* externSymbolTable, char* errorMessage)
{
	Boolean parseSucceeded = FALSE;

	/* Search for the given symbol in the local symbol table */
	Symbol* matchingSymbol = GetSymbolByName(operand->operandBaseSymbol, symbolTable);

	if (matchingSymbol != NULL)
	{
		operand->operandBaseWord = calculateOperandAffectiveAddress(matchingSymbol, symbolTable);
		operand->linkingOptionBaseWord = RELOCATABLE;
		parseSucceeded = TRUE;
	}
	else
	{
		/* Search for the given symbol in the external symbol table */
		matchingSymbol = GetSymbolByName(operand->operandBaseSymbol, externSymbolTable);

		if (matchingSymbol != NULL)
		{
			operand->operandBaseWord = calculateOperandAffectiveAddress(matchingSymbol, symbolTable);
			operand->linkingOptionBaseWord = EXTERNAL;

			parseSucceeded = TRUE;
		}
		else
		{
			sprintf(errorMessage , "undefined symbol");
		}
	}

	return parseSucceeded;
}

Boolean ParseOperandExtendWord(Operand* operand, SymbolTable* symbolTable, SymbolTable* externSymbolTable, char* errorMessage)
{
	Boolean parseSucceeded = FALSE;

	/* Search for the given symbol in the local symbol table */
	Symbol* matchingSymbol = GetSymbolByName(operand->operandExtendSymbol, symbolTable);

	if (matchingSymbol != NULL)
	{
		operand->operandExtendWord = calculateOperandAffectiveAddress(matchingSymbol, symbolTable);
		operand->linkingOptionExtendWord = RELOCATABLE;
		parseSucceeded = TRUE;
	}
	else
	{
		/* Search for the given symbol in the external symbol table */
		matchingSymbol = GetSymbolByName(operand->operandExtendSymbol, externSymbolTable);

		if (matchingSymbol != NULL)
		{
			operand->operandExtendWord = calculateOperandAffectiveAddress(matchingSymbol, symbolTable);
			operand->linkingOptionExtendWord = EXTERNAL;
			parseSucceeded = TRUE;
		}
		else
		{
			sprintf(errorMessage , "undefined symbol");
		}
	}

	return parseSucceeded;
}

InstructionMemoryWord* EncodeOperand(Operand* currOperand, InstructionMemoryWord* currMemoryWord, InstructionMemoryWord* commandWord, int index)
{
	int ExtraWordsUsed = 0;
	AddressingMethodDetail addressingMethodDetail =
			GetAddressingMethodDetail(currOperand->operandAdressingMethod);

	if (index == DEST_OPERAND)
	{
		commandWord->machineCode.wordEncoding.CodeWord.destinationAddressingMethod = addressingMethodDetail.opcode;
	}
	else
	{
		commandWord->machineCode.wordEncoding.CodeWord.sourceAddressingMethod = addressingMethodDetail.opcode;
	}

	switch(currOperand->operandAdressingMethod)
	{
	case IMMEDIATE_ADDRESSING:
	case DIRECT_ADDRESSING:
		currMemoryWord->machineCode.wordEncoding.DataWord = currOperand->operandBaseWord;
		currMemoryWord->linkInformation = currOperand->linkingOptionBaseWord;
		ExtraWordsUsed = 1;
		break;
	case INDEX_ADDRESSING:
		currMemoryWord->machineCode.wordEncoding.DataWord = currOperand->operandBaseWord;
		currMemoryWord->linkInformation = currOperand->linkingOptionBaseWord;
		(currMemoryWord + 1)->machineCode.wordEncoding.DataWord = currOperand->operandExtendWord;
		(currMemoryWord + 1)->linkInformation = currOperand->linkingOptionExtendWord;
		ExtraWordsUsed = 2;
		break;
	case DOUBLE_INDEX_ADDRESSING:
		currMemoryWord->machineCode.wordEncoding.DataWord = currOperand->operandBaseWord;
		currMemoryWord->linkInformation = currOperand->linkingOptionBaseWord;
		(currMemoryWord + 1)->machineCode.wordEncoding.DataWord = currOperand->operandExtendWord;
		(currMemoryWord + 1)->linkInformation = currOperand->linkingOptionExtendWord;
		if (index == DEST_OPERAND)
		{
			commandWord->machineCode.wordEncoding.CodeWord.destinationRegister = currOperand->registerOperand.registerAddress;
		}
		else
		{
			commandWord->machineCode.wordEncoding.CodeWord.sourceRegister = currOperand->registerOperand.registerAddress;
		}
		ExtraWordsUsed = 2;
		break;
	case REGISTER_ADDRESSING:
		if (index == DEST_OPERAND)
		{
			commandWord->machineCode.wordEncoding.CodeWord.destinationRegister = currOperand->operandBaseWord;
		}
		else
		{
			commandWord->machineCode.wordEncoding.CodeWord.sourceRegister = currOperand->operandBaseWord;
		}
		break;
	default:
		break;
	}

	return (currMemoryWord + ExtraWordsUsed);
}

void AddSymbolToExternRefrence(Operand* operand, int operandIndex, int SourceOpernadSize, SymbolTable* externRefrenceTable, int currentInstructionAddress)
{
	int operandAddressOffset = 1;
	
	if (operandIndex == DEST_OPERAND)
	{
		operandAddressOffset += SourceOpernadSize;
	}
	
	if (operand->linkingOptionBaseWord == EXTERNAL)
	{
		strcpy(externRefrenceTable->symbols[externRefrenceTable->SC].symbolName, operand->operandBaseSymbol);
		
		externRefrenceTable->symbols[externRefrenceTable->SC++].offset = currentInstructionAddress + operandAddressOffset;
	}

	if (operand->linkingOptionExtendWord == EXTERNAL)
	{
		strcpy(externRefrenceTable->symbols[externRefrenceTable->SC].symbolName, operand->operandExtendSymbol);

		externRefrenceTable->symbols[externRefrenceTable->SC++].offset = currentInstructionAddress + operandAddressOffset + 1;
	}
}

InstructionMemoryWord* EncodeCommand(CommandOperands* command, InstructionMemoryWord* MemoryWord, SymbolTable* externRefrenceTable, int currentInstructionAddress)
{
	InstructionMemoryWord* nextMemoryWord = MemoryWord;

	if (command != NULL)
	{
		int operandIndex;
		int sourceOperandSize = 0;
		CommandDetail* commandDetail = GetCommandDetailByType(command->commandType);
		InstructionMemoryWord* originalCommand = nextMemoryWord;

		/* mark its linking option */
		originalCommand->linkInformation = ABSOLUTE;

		nextMemoryWord->machineCode.wordEncoding.DataWord = 0;

		/* reserve one word for the command*/
		nextMemoryWord++;

		/* start encoding */
		originalCommand->machineCode.wordEncoding.CodeWord.opcode = commandDetail->commandOpcode;
		
		/* if there is no source operand it size is zero */
		sourceOperandSize = (command->operandCount == 2) ? GetAddressingMethodDetail(command->operands[SOURCE_OPERAND].operandAdressingMethod).numberOfWordUsed : 0;

		for (operandIndex = command->operandCount - 1; operandIndex >= 0; operandIndex--)
		{
			Operand currOperand = command->operands[operandIndex];
			nextMemoryWord = EncodeOperand(&currOperand, nextMemoryWord, originalCommand, operandIndex);

			AddSymbolToExternRefrence(&currOperand, operandIndex, sourceOperandSize, externRefrenceTable, currentInstructionAddress);
		}

		///* TODO: change back to loop */
		//{
		//	if (command->operandCount == 2)
		//	{
		//		Operand currOperand = command->operands[1];
		//		nextMemoryWord = EncodeOperand(&currOperand, nextMemoryWord, originalCommand, 1);

		//		currOperand = command->operands[0];
		//		nextMemoryWord = EncodeOperand(&currOperand, nextMemoryWord, originalCommand, 0);
		//	}
		//	else
		//	{
		//		if (command->operandCount > 0)
		//		{
		//			Operand currOperand = command->operands[0];
		//			nextMemoryWord = EncodeOperand(&currOperand, nextMemoryWord, originalCommand, 0);
		//		}
		//	}
		//}
	}

	return nextMemoryWord;
}

Boolean PerformSecondPass(ProgramLines* programLines, Memory* programMemory,
						  SymbolTable* symbolTable, SymbolTable* externSymbolTable, SymbolTable* entryRefrenceTable, SymbolTable* externRefrenceTable)
{
	Boolean secondPassSucceeded = FALSE;
	Boolean errorFound = FALSE;

	InstructionMemoryWord* nextWordInMemory = programMemory->codeSegment;
	int index;

	int instructionCount = 0;

	programMemory->codeBaseAddress = STARTING_MEMORY_ADDRESS;
	symbolTable->symbolBaseAdress = programMemory->IC + programMemory->codeBaseAddress;

	for (index = 0; index < programLines->lineCounter; index++)
	{
		/* Get the current line */
		ParsedProgramLine* currProgramLine = &(programLines->lines[index]);

		/* Check if an earlier error was found so that the output files wouldn't be created */
		if (currProgramLine->parseErrorOccured)
		{
			errorFound = TRUE;
		}

		if (currProgramLine->shouldBeHandled == TRUE)
		{
			if (currProgramLine->commadParsedOperands != NULL)
			{
				int operandIndex;
				for (operandIndex = 0;
						operandIndex < currProgramLine->commadParsedOperands->operandCount; operandIndex++)
				{
					Operand* currOperand =
							&(currProgramLine->commadParsedOperands->operands[operandIndex]);

					if (ParseSecondPassOperand(programMemory->codeBaseAddress, nextWordInMemory, instructionCount, 
							currOperand, symbolTable, externSymbolTable,  currProgramLine->errorMessage) != TRUE)
					{
						errorFound = TRUE;
						sprintf(currProgramLine->errorMessage ,  "Entry symbol wasn't found");
					}
				}
				nextWordInMemory = EncodeCommand(currProgramLine->commadParsedOperands, nextWordInMemory, externRefrenceTable, instructionCount + programMemory->codeBaseAddress);

				instructionCount += currProgramLine->commadParsedOperands->sizeInWords;
			}
			else
			{
				/* check if entry */
				if (strcmp(currProgramLine->commandTokens->statement.directive, ENTRY_DIRECTIVE) == EQUAL_STRINGS)
				{
					if (currProgramLine->commandTokens->params.paramCounter == EXPECTED_ENTRY_PARAM_COUNT)
					{
						char* referencedSymbolName = currProgramLine->commandTokens->params.paramValues[ENTRY_PARAM_SYMBOL_LOCATION];

						Symbol* entrySymbol = GetSymbolByName(referencedSymbolName, symbolTable);
						if (entrySymbol == NULL)
						{
							errorFound = TRUE;
							sprintf(currProgramLine->errorMessage ,  "Entry symbol wasn't created");
						}
						else
						{
							entryRefrenceTable->symbols[entryRefrenceTable->SC] = *entrySymbol;
							entryRefrenceTable->symbols[entryRefrenceTable->SC].offset = calculateOperandAffectiveAddress(entrySymbol, symbolTable);
							entryRefrenceTable->SC++;
						}
					}
					else
					{
						errorFound = TRUE;
						sprintf(currProgramLine->errorMessage ,  "Entry symbol wasn't created");
					}
				}
				else if (strcmp(currProgramLine->commandTokens->statement.directive, EXTERN_DIRECTIVE) == EQUAL_STRINGS)
				{
					/* Do nothing */
				}
			}
		}
	}

	if (errorFound == FALSE)
	{
		secondPassSucceeded = TRUE;
	}

	return secondPassSucceeded;
}

/* ----------------------------------------------------------------------------
 *	Function Definition End
 * ----------------------------------------------------------------------------*/
